package server;

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.HashMap;
import java.util.Map;
import client.Message;
import chatInterface.Interface;

public class ServerImpl implements Interface {
       
        private String name;
        private String ip;
        private int port;
        private static Map<String, NodeRecord> records = new HashMap<String, NodeRecord>();
        private String myname;
        private String myip;
        private int myport;
        private int myID;  //Identifikacni cislo serveru
        private static NodeRecord first=null;
        private static NodeRecord second=null; //Aktivni uzly pri propojeni v kruhu
        private Interface chsClient;
        private int numNodes=0;
        //Promene k Leadrovi
        private boolean RespOK;
        private VectorClock clock;
        private int firstSend=0;  //0- prvni poslani 1-poslani poctu uzlu
        private int Nresp,Winner,State,Lmax,l; //State: 0-Not_Involved 1-Candidate 2-Elected 3-Lost
        //Konec promene k Leadrovi
        private Registry chsRegistry;
       
        ServerImpl(String Myname, String Myip, int Myport){
                this.myip=Myip;
                this.myname=Myname;
                this.myport=Myport;    
                createID();
        }
       
        ServerImpl(String Myname, String Myip, int Myport,String name, String ip, int port){
                this.ip=ip;
                this.name=name;
                this.port=port;
                createID();
                this.myip=Myip;
                this.myname=Myname;
                this.myport=Myport;
        }
       
        // VYBER LEADERU POUZE PRIPRAVA NEZAPOJENO  ZACATEK//
       
       
        //metoda ktera zajisti ze vsechny uzly se ocisluji a nastavi pocet uzlu a vytvori logicke logicke
        public void prepareForComunication(int numNodes, int myID) throws RemoteException{
                if(this.firstSend==0){
                numNodes++;
                myID++;
                this.myID=myID;
                first.getInterface().prepareForComunication(numNodes,myID);
                this.firstSend=1;
                }
                else if(this.firstSend==1){
                        this.numNodes=numNodes;
                        first.getInterface().prepareForComunication(numNodes,myID);
                        this.firstSend=2;
                        this.clock=new VectorClock(this.numNodes,this.myID);
                }
        }
       
        //inicializace leaderelection
        private void initLeaderElection (){
                Nresp=0;
                RespOK=true;
                State=0;
                Lmax=0;
                l=0;
                Winner=0;
        }
       
        //zahajeni vyberu, provadi uzel
        public void beginLeaderElectionFirst(){
                State=1;
                Lmax=1;
                while(State==1){
                        Nresp=0;
                        RespOK=true;
                        sendCandidature(myID,Lmax);
                        while(true){
                                if(Nresp==2)break;
                        }
                        if(RespOK==false)State=3;
                        Lmax=2*Lmax;            
                }
        }
       

       
        private boolean sendCandidature(int ids, int Lmaxs){
                try {
                        this.first.getInterface().processCandidatire(ids, 0, Lmaxs);
                        this.second.getInterface().processCandidatire(ids, 0, Lmaxs);
                        return true;
                } catch (RemoteException e) {
                        e.printStackTrace();
                }
                return true;
        }
       
        //Preposilaci metody
        public void forwardCandidature (int id, int num, int lmax,String name){
               
        }
       
        public void forwardResponse (boolean r, int j,String name){
               
        }
       
        public void forwardElected (int id,String name){
               
        }
       
        public void receivedResponse(boolean r, int j, String name){
                if(j==myID){
                        Nresp = Nresp+1;
                        RespOK=RespOK && r;
                       
                }else{
                        forwardResponse(r,j,name);
                        // pass Response(r,j);
                }
        }
       
        public void receivedElected (int j, String name){
                if(Winner!=j){
                        forwardElected(j,name);
                        Winner=j;
                        State=0;
                }
               
        }
       
        private void sendResponse (boolean resp, int j){
               
        }
       
        public void processCandidatire(int j, int lr, int Lmaxr) throws RemoteException{
                if(j<myID){
                        sendResponse(false,j);
                        if(State==0){
                                initLeaderElection();
                                beginLeaderElectionFirst();
                        }
                }
                else if(j>myID){
                        State=3;
                        lr=lr+1;
                        if(lr<Lmaxr){
                                forwardCandidature(j,lr,Lmaxr,name);
                                //pass CANDIDATURE(j,lr,lmaxr)
                        }
                        else{
                                sendResponse(true,j);
                        }
                }else if(j==myID){
                        if(State!=2){
                                State=2;
                                Winner=myID;
                                forwardElected(myID,name);
                        }
       
                }
        }
       
        // VYBER LEADERU POUZE PRIPRAVA NEZAPOJENO  KONEC//
       
        public void sendMessage(Message msg) throws RemoteException {
                System.out.println("aaaaa");
        }
       
        public Map<String, NodeRecord> getContacts() throws RemoteException {
                return records;
        }
       
        public String hello() throws RemoteException {
                String msg = "Ahoj od " + myname + " " + "(" + myip + ":" + myport + ")";
                return msg;
        }
       
        public boolean connectToServer(String name, String ip, int port) throws RemoteException {
                this.ip=ip;
                this.port=port;
                this.name=name;
                try {
                        createRemoteClient();
                } catch (Exception e) {
                        return false;
                }
                return true;
        }
       
        //Metoda na vytvoreni klientskeho spojeni s druhym uzlem a prikaz na vytvoreni klientskemu pripojeni ke me
        public void createLocalClient(){
                try {
                        chsRegistry = LocateRegistry.getRegistry(ip, port);
                        chsClient =  (Interface) chsRegistry.lookup(name);//Naming.lookup("//" + ip + ":" + port + "/"+name);
                        System.out.print("Jdu se pripojit k: " +name +" takze se pozdravime ");
                        System.out.println( chsClient.hello());
                        NodeRecord node = new NodeRecord(name,ip,port);
                        node.setConnected(true, chsRegistry, chsClient);
                        records.put(name, node);
                        first=node;
                        chsClient.connectToServer(myname, myip, myport);
                } catch (RemoteException  | NotBoundException e) {      
                        e.printStackTrace();
                }
        }
       
        public void createRemoteClient(){
                try {
                        chsRegistry = LocateRegistry.getRegistry(ip, port);
                        chsClient =  (Interface)chsRegistry.lookup(name); // Naming.lookup("//" + ip + ":" + port + "/"+name);
                        NodeRecord node = new NodeRecord(name,ip,port);
                        node.setConnected(true, chsRegistry, chsClient);
                        records.put(name, node);
                       
                        //serie podminek pro zjisteni v jakem stavu pripojeni je kruh a nasledne vytvoreni kruhu
                        if(first==null){
                                first=node;
                                System.out.println("Nasel mne prvni: " +node.getName());
                                System.out.println("Mam tedy spojeni s: "+first.getName());
                        }else if(second==null){
                                second=node;
                                System.out.print("Nasel mne druhy: " +node.getName());
                                chsClient.sendBuddy(first);
                                System.out.println(" poslu mu: " +first.getName());
                                first.getInterface().sendBuddy(node);
                                System.out.println("Mam tedy spojeni s: "+first.getName()+" a "+second.getName());
                        }else {
                                System.out.print("Nasel mne:" +node.getName());
                                System.out.println(" tak se snim pozdravim: " +second.getInterface().hello());
                                System.out.println("To znamena ze uz nechci: " +second.getName());
                                second.getInterface().disconnectMe(myname);
                                chsClient.sendBuddy(second);
                                System.out.print(" a posilam mu novehoveho:" +node.getName());
                                second.getInterface().sendBuddy(node);
                                records.get(second.getName()).setConnect(false);
                                second=node;
                                System.out.println("Mam tedy spojeni s: "+first.getName()+" a "+second.getName());
                        }
                } catch (RemoteException | NotBoundException  e) {      
                        e.printStackTrace();
                        System.out.println(e.getMessage());
                }
        }

        public String getMyname() {
                return myname;
        }

        public String getMyip() {
                return myip;
        }

        public int getMyport() {
                return myport;
        }

        // Metoda pomoci ktere vytvorim spojeni s dalsim uzlem, 
        // ktery je mi predan od uzlu ktery je uz ke mne pripojeny
        public void sendBuddy(NodeRecord node) throws RemoteException {
                try {
                        chsRegistry = LocateRegistry.getRegistry(node.getIp(), node.getPort());
                        chsClient =  (Interface) chsRegistry.lookup(node.getName());//Naming.lookup("//" + node.getIp() + ":" + node.getPort() + "/"+node.getName());
                        node.setConnected(true, chsRegistry, chsClient);
                        records.put(node.getName(), node);
                        if(second==null)second=node;
                        else first=node;
                        System.out.println("Prisel mi novy uzel:" +node.getName()+" tak se snim pozdravim "+node.getInterface().hello());
                        System.out.println("Mam tedy spoj s: "+first.getName()+" a "+second.getName());
                       
                } catch (NotBoundException e) {
                        e.printStackTrace();
                }
        }

        @Override
        //Metoda ktera me odpoji od uzlu co si to vyzadal
        public void disconnectMe(String myname) throws RemoteException {
                System.out.println("Odpojuji se od :" +myname);
                if(first.getName().equals(myname)){
                        records.get(first.getName()).setConnect(false);
                        first=null;
                        System.out.println("Mam tedy zatim spoj s: "+second.getName());
                }
                else {
                        records.get(second.getName()).setConnect(false);
                        second=null;
                        System.out.println("Mam tedy zatim spoj s: "+first.getName());
                }
        }
       
        //Metoda ktera vytvari ID serveru
        public void createID (){
                myID=myname.hashCode()*myip.hashCode()*myport;
        }
}
